package org.juiceshop.musi;

import java.util.Arrays;


public class KeySignature {

	private static final int[] SHARP_TABLE = { 5, 0, 7, 2, 9, 4 }; // F# C# G# D# A# E#
	private static final int[] FLAT_TABLE = { 11, 4, 9, 2, 7 }; // Bb Eb Ab Db Gb
	private static final String[] MAJOR_NOTES = {"C","Db","D","Eb","E","F","F#","G","Ab","A","Bb","B"};
	private static final String[] MINOR_NOTES = {"C","C#","D","D#","E","F","F#","G","G#","A","Bb","B"};
	private static final int[] MAJOR_SCALE = {0,2,4,5,7,9,11};
	private static final int[] MINOR_SCALE = {0,2,3,5,7,8,10};
	

	private static final int[] MIDI_SYMBOLS = {
		+0, //C-major	A-minor
		-4, //Db-major  Bb-minor
		+2, //D-major	B-minor
		-3, //Eb-major	C-minor
		+4, //E-major	C#-minor
		-1, //F-major	D-minor
		+6, //F#-major	D#-minor
		+1, //G-major	E-minor
		-4, //Ab-major	F-minor
		+3, //A-major	F#-minor
		-2, //Bb-major	G-minor
		+5  //B-major	G#-minor
	};

	
	public static KeySignature major(int pitch) {
		return new KeySignature(pitch%12,false);
	}
	
	public static KeySignature major(String note) {
		int pitch = Util.findInArray(MAJOR_NOTES,note);
		if (pitch<0) throw new IllegalArgumentException("Unknown note: "+note);
		return major(pitch);
	}
	
	public static KeySignature minor(int pitch) {
		return new KeySignature(pitch%12,true);
	}
	
	public static KeySignature minor(String note) {
		int pitch = Util.findInArray(MINOR_NOTES,note);
		if (pitch<0) throw new IllegalArgumentException("Unknown note: "+note);
		return minor(pitch);
	}
			
	private final int _base;
	private final String _name;
	private final int[] _table;
	private final int _tsize;
	private final int[] _scale;
	private final int _midiSymbol;
	private final int _adjustment;

	private KeySignature(int base, boolean minor) {
		_base = base;
		int majorBase = minor ? (base+3)%12 : base;
		_midiSymbol = MIDI_SYMBOLS[majorBase];
		if (_midiSymbol < 0) {
			_table = FLAT_TABLE;
			_tsize = -_midiSymbol;
			_adjustment = -1;
		} else {
			_table = SHARP_TABLE;
			_tsize = _midiSymbol;
			_adjustment = 1;
		}
		if (minor) {
			_scale = copyScale(MINOR_SCALE,base);
			_name = MINOR_NOTES[base]+"-minor";
		} else {
			_scale = copyScale(MAJOR_SCALE,base);
			_name = MAJOR_NOTES[base]+"-major";
		}
	}
	
	private static int[] copyScale(int[] scale, int base) {
		int[] newScale = new int[scale.length];
		for (int i=0; i<scale.length; i++) {
			newScale[i] = (scale[i]+base)%12;
		}
		Arrays.sort(newScale);
		return newScale;
	}

	public boolean isInTable(int pitch) {
		int x = pitch%12;
		for (int i=0; i<_tsize; i++) {
			if (x==_table[i]) return true;
		}
		return false;
	}

	public boolean isInScale(int pitch) {
		return Arrays.binarySearch(_scale, pitch%12)>=0;
	}
	
	public String getName() {
		return _name;
	}

	public int[] getScale() {
		return _scale;
	}
	
	public int getMidiSymbol() {
		return _midiSymbol;
	}

	public int adjust(int pitch) {
		return pitch + (isInTable(pitch)?_adjustment:0);
	}

	public int natural(int pitch) {
		int newPitch = pitch-_adjustment;
		return isInTable(newPitch) | !isInScale(pitch) ? newPitch : pitch;
	}

	/**
	 * Scalar transposition.
	 * @param pitch
	 * @param steps
	 * @return
	 */
	public int scale (int pitch, int steps) {
		int note = pitch%12;
		int octave = pitch-note;
		int adjust = 0;
		int index = Arrays.binarySearch(_scale, note);
		if (index<0) {
			index = -index-1;
			adjust = _adjustment;
			if (adjust>0) index--;
		}
		index += steps;
		while (index<0) {
			index += _scale.length;
			octave -= 12;
		}
		while (index>=_scale.length) {
			index -= _scale.length;
			octave += 12;
		}
		int newNote = octave+_scale[index]+adjust;
		return newNote;
	}
	
	public KeySignature transpose(int steps) {
		int newBase = (_base+steps)%12;
		if (newBase<0) newBase+=12;
		return new KeySignature(newBase,_scale==MINOR_SCALE);
	}
	
	@Override
	public String toString() {
		return _name;
	}
	
	@Override
	public boolean equals(Object obj) {
		return (obj instanceof KeySignature && obj.toString().equals(this.toString()));
	}
	
	@Override
	public int hashCode() {
		return this.toString().hashCode();
	}
		
}
